#define COMPILING_LOGGERLIB
#include "Logger.h"
#include "LogServer.h"
#include <stdio.h>
#include <crtdbg.h>

#ifdef _DEBUG
#include <tchar.h>
#endif


//////////////////////////////////////////////////
//  The implementation
//////////////////////////////////////////////////////////
static const int		LOG_TIMEOUT =		15000;  // timeout in 15 secs.

static bool				isInitialized = false;
LOGIMPORT bool         g_isLoggingEnabled = true;

static TLOGINITIALIZE	theInitInfo;
static HANDLE			theMapfile =		NULL;
extern TLOGBUFFHEADER	*theSharedMemory =	NULL;
static HANDLE			theMutexA =			NULL;
static HANDLE			theMutexB =			NULL;
static HANDLE			theStartSignal =	NULL;
static HANDLE			theWaitSignal =		NULL;
static DWORD            theLogNumber  =     0;     // wrap around log number to identify individual, multi-line logs.


// error codes
typedef enum TLOGERR
{
	LOGERR_SUCCESS =		0,
	LOGERR_NOSERVER =		-1,
	LOGERR_WRONGVERSION =	-2,
	LOGERR_INTERNAL =		-3,
	LOGERR_SUPPRESSED =		-4,
} TLOGERR;


#ifdef _DEBUG
static TLOGERR NotifyError(TLOGERR err)
{
	if (err == LOGERR_WRONGVERSION)
		OutputDebugString(
		_T("*** Log library and log server have different version number\n"));

	if (err == LOGERR_INTERNAL)
		OutputDebugString(
		_T("*** Log library failed to initialize for no apparent reason\n"));

	Log__TermLibrary();
	
	return err;
}

#define NOTIFYERROR(err) NotifyError(err)
#else // _DEBUG
#define NOTIFYERROR(err) err
#endif // _DEBUG


static bool ChannelInit(TLOGBUFFHEADER *shared, TLOGOBJECT &logobj);


bool Log__IsLogging() {return isInitialized;}

//CCP Note!  changed to LONG_PTR rather than long
int Log__InitLibrary(LONG_PTR module, const char *logdevname)
{
	_ASSERTE(!isInitialized);

	// get the shared memory
	theMapfile = OpenFileMappingA(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, logdevname);
	if (!theMapfile)
		return LOGERR_NOSERVER;

	// initialize names used for accessing the memory map and mutexes
	TLOGDEVNAMES names;
	LogInitDevNames(logdevname, names);

	// get the name of this computer
	DWORD tmp = sizeof(theInitInfo.computer);
	GetComputerNameA(theInitInfo.computer, &tmp);

	// get the process id
	theInitInfo.pid = GetCurrentProcessId();

	// the the name of the process (application name)
	GetModuleFileNameA(
		0, 
		theInitInfo.processname, 
		sizeof (theInitInfo.processname));

	// get the name of this module
	theInitInfo.module = (DWORD)module; //can't pass a 64 bit pointer down.
	GetModuleFileNameA(
		(HMODULE)module, 
		theInitInfo.modulename, 
		sizeof (theInitInfo.modulename));

	// get the start time
	QueryPerformanceCounter(&theInitInfo.starttime);

	theSharedMemory = (TLOGBUFFHEADER*)MapViewOfFile(
		theMapfile,
		FILE_MAP_READ|FILE_MAP_WRITE,
		0, 0,
		0
		);

	_ASSERTE(theSharedMemory);

	// check version
	if (HIWORD(theSharedMemory->version) != HIWORD(LOG_VERSION))
		return NOTIFYERROR(LOGERR_WRONGVERSION);

	theStartSignal = OpenEventA(EVENT_ALL_ACCESS, TRUE, names.fulleventname);
	theWaitSignal = OpenEventA(EVENT_ALL_ACCESS, TRUE, names.flusheventname);
	theMutexA = OpenMutexA(MUTEX_ALL_ACCESS, TRUE, names.mutexnamea);
	theMutexB = OpenMutexA(MUTEX_ALL_ACCESS, TRUE, names.mutexnameb);

	if (!theStartSignal || !theWaitSignal || !theMutexA || !theMutexB)
		return NOTIFYERROR(LOGERR_INTERNAL);

	isInitialized = true;

	return LOGERR_SUCCESS;
}


void Log__TermLibrary()
{
	isInitialized = false;

	if (theSharedMemory)
		UnmapViewOfFile(theSharedMemory);
	
	if (theMapfile)
		CloseHandle(theMapfile);

	if (theStartSignal)
		CloseHandle(theStartSignal);

	if (theWaitSignal)
		CloseHandle(theWaitSignal);

	if (theMutexA)
		CloseHandle(theMutexA);

	if (theMutexB)
		CloseHandle(theMutexB);

	theMapfile =		NULL;
	theSharedMemory =	NULL;
	theMutexA =		NULL;
	theMutexB =		NULL;
	theStartSignal =	NULL;
	theWaitSignal =		NULL;
}


namespace {
	static bool Lock()
	{
		DWORD wait;

		wait = WaitForSingleObject(theMutexB, LOG_TIMEOUT);

		if (wait != WAIT_OBJECT_0 && wait != WAIT_ABANDONED)
		{
			Log__TermLibrary();
			return false;
		}


		wait = WaitForSingleObject(theMutexA, LOG_TIMEOUT);

		if (wait != WAIT_OBJECT_0 && wait != WAIT_ABANDONED)
		{
			Log__TermLibrary();
			return false;
		}

		return isInitialized ? true : false;
	}

	static void Unlock()
	{
		ReleaseMutex(theMutexA);
		ReleaseMutex(theMutexB);
	}
}


/* Introduced individual log numbers by re-using the TID field.
 * In order to get multi-line log messages to have a single identifying number, 
 * they must be called with the 0x100 value set for the lfn.msg.flag for every
 * subsequent line, except the first line in the log message. This is handled
 * in the log.py code. 
 *
 * If this is done, then the log will treated as a multi-line log, and given the 
 * same TID number. The 0x100 bit is then cleared before the log is written out to 
 * shared memory in order  to preserve backwards compatability with existing 
 * post-processing as of November 2009
 */


//called with the mutexex B and A acquired.
static bool MarshalCall(TLOGFUNCTION &lfn, size_t len)
{
	volatile TLOGBUFFHEADER *shared = theSharedMemory;

	// fill in the standard info

       /* Detect multi line logs, and tag them with the same
        * TID number so that post processing can filter them
        */

        if(lfn.msg.flag & MLOG_FLAG)
	{
	   lfn.tid = theLogNumber;
           // Remove multi-line flag before writing to memory.
	   lfn.msg.flag = (TLOGFLAG)(lfn.msg.flag & 0xff);           
                                                             
 							    
	}
	/* '-' is put at the front of multi line logs somewhat independently
	 * in several places, so try to catch it here. Thread id is used to
         * prevent repercussions elsewhere. 
	 */
	else if(lfn.msg.msg[0] == '-')
	{
		lfn.tid = theLogNumber;
	}
	else
	{
	        lfn.tid = ++theLogNumber;    
	}

	// "marshal" the data
	if (shared->numRecs == shared->capacity)
	{
		// must flush the buffer now. set signals, then release mutex so that logger will run
		SetEvent(theStartSignal);
		ResetEvent(theWaitSignal);
		ReleaseMutex(theMutexA);
		
		// wait to restart
		if (WaitForSingleObject(theWaitSignal, LOG_TIMEOUT) != WAIT_OBJECT_0 ||
			WaitForSingleObject(theMutexA, LOG_TIMEOUT) != WAIT_OBJECT_0)
		{
			Log__TermLibrary();
			return false;
		}
	}

	TLOGFUNCTION *f = (TLOGFUNCTION*) &(shared[1]); //functions start right after header
	DWORD idx = shared->numRecs++;
	memcpy(&f[idx], &lfn, len);
	//stupid! why is pid after the variable length message???
	f[idx].msg.pid = lfn.msg.pid;
	return true;
}


static inline __int64 HashString(const char *str)
{
	__int64 v = 0;
	int i = 0;
	while (*str && i++ < 31)
	{
		v = (v << 5) + (*str++ &~ 0x20) - 'A';
	}

	return v;
}

/*
#define SH(i) ((str[i] &~ 0x20) - 'A')) << 5)

static inline __int64 HashString2(const char *str)
{
	
	return
			((((((((((((((((((((((((
				SH(0) + 
				SH(1) + 
				SH(2) + 
				SH(3) + 
				SH(4) + 
				SH(5) + 
				SH(6) + 
				SH(7) + 
				SH(8) + 
				SH(9) + 
				SH(10) + 
				SH(11);
}
*/


void Log__global(
	TLOGOBJECT &logobj,
	TLOGFLAG flag,
	const char *format, 
	va_list argptr
	)
{
	if (!isInitialized)
		return; //sometimes library init fails, when the logger isn't available.

	
	 TLOGBUFFHEADER *shared = theSharedMemory;
	if (logobj.channel == 0) {
		if (!ChannelInit(shared, logobj))
			return;
	}

	volatile TLOGCHANNELINFO *ch = shared->logchannels.channels + logobj.channel-1;

	// check flag suppression
	// Code check suggests this isn't being used -- jm


	if (ch->flagsuppress & flag)
		return;
	
	TLOGFUNCTION lfn;
	QueryPerformanceCounter(&lfn.timestamp);

	lfn.channel = logobj.channel;
	lfn.source = logobj.source;
	lfn.fntype = LOGFN_TRACEMESSAGE;
// jm	lfn.tid = 0;
	
	lfn.msg.flag = flag;
	lfn.msg.pid = theInitInfo.pid;

	size_t msglen = vsnprintf_s(lfn.msg.msg, LOG_MAXMESSAGE, _TRUNCATE, format, argptr);
	if (msglen == -1)
		msglen = LOG_MAXMESSAGE-1;
	
	// send the trace message
	if (!Lock())
		return;

	msglen += offsetof(TLOGFUNCTION, msg.msg)+1;
	if (!MarshalCall(lfn, msglen))
		return;

	Unlock();
}


bool ChannelInit(
	TLOGBUFFHEADER *shared,
	TLOGOBJECT &logobj)
{
	// send initialize message
	TLOGCHANNELINFO info;
	info.facilityhash = HashString(logobj.facility);
	info.objecthash = HashString(logobj.object);

	// find a free channel
	if (!Lock())
		return false;

	TLOGCHANNELS &lch = shared->logchannels;
	TLOGCHANNELINFO *ch = lch.channels;
	
	int i;
    for (i = 0; i < lch.numchannels; i++)
	{
		if (ch->facilityhash == info.facilityhash && 
			ch->objecthash == info.objecthash)
			break;
		ch++;
	}

	logobj.channel = i+1;		    //the possibly shared channel id
	logobj.source = lch.counter++;  //an unique source id.
	
	if (i == lch.numchannels)
	{
		// add it, if there is space left on the device
		if (lch.numchannels >= LOG_MAXCHANNELS)
		{
			logobj.oktocall = 0;
			Unlock();
			return false;
		}
		lch.numchannels++;
		
		strncpy_s(info.facility, _countof(info.facility), logobj.facility, _TRUNCATE);
		strncpy_s(info.objectdesc, _countof(info.objectdesc), logobj.object, _TRUNCATE);
		info.flagsuppress = 0;
		info.suppressed = 0;
		*ch = info;
	}

	// is this channel out of service?
	if (ch->suppressed)
	{
		logobj.oktocall = 0;
		Unlock();
		return false;
	}

	// send the init message
	TLOGFUNCTION lfn;
	QueryPerformanceCounter(&lfn.timestamp);
	lfn.init = theInitInfo;
	lfn.channel = logobj.channel;
	lfn.source = logobj.source;
	lfn.fntype = LOGFN_INITIALIZE;
	lfn.tid = 0;

	size_t len = offsetof(TLOGFUNCTION, init) + sizeof(lfn.init);
	if (!MarshalCall(lfn, len))
		return false;
	Unlock();
	return true;
}



/*

  // This optimization is not worth it.  LOGF took 5711 and 5130
  // and LOG took 4145.
  // The customized strncpy runs 1000% faster than vsntprintf, but
  // the LOG function as a whole only runs about 20% faster.  Given
  // that most log calls will include some varargs, the absolute
  // total difference migth be around 2-3%.  The ease of use and
  // having two log functions instead of four (counting the DEBUG
  // versions as well) is more appealing than couple of percents
  // off in time spent in the log library.

void LogTrace__private(TLOGCHANNEL channel, LPCTSTR string)
{
	TLOGFUNCTION lfn;

	// setup parameters for the "function call"
	lfn.fntype = LOGFN_TRACEMESSAGE;
	lfn.message.channel = channel;
	
	unsigned len = _tcslen(string) + 1;
	
	memcpy(
		lfn.message.message, 
		string, 
		sizeof (TCHAR) * LOG_MAXMESSAGE-1 < len ? LOG_MAXMESSAGE-1 : len);

	lfn.message.message[LOG_MAXMESSAGE-1] = _T('\0');

	MarshalCall(&lfn);
}

*/

